package com.twotoasters.watchface.gears.widget; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.SystemClock; import android.provider.Settings; import android.support.annotation.NonNull; import android.text.TextUtils; import android.text.format.DateFormat; import android.view.ViewDebug.ExportedProperty; import java.lang.ref.WeakReference; import java.util.Calendar; import java.util.Locale; import java.util.TimeZone; public class Watch { private static final String ACTION_KEEP_WATCHFACE_AWAKE = "intent.action.keep.watchface.awake"; /** * The default formatting pattern in 12-hour mode. This pattern is used * if {@link #setFormat12Hour(CharSequence)} is called with a null pattern * or if no pattern was specified when creating an instance of this class. * * This default pattern shows only the time, hours and minutes, and an am/pm * indicator. * * @see #setFormat12Hour(CharSequence) * @see #getFormat12Hour() * * @deprecated Let the system use locale-appropriate defaults instead. */ public static final CharSequence DEFAULT_FORMAT_12_HOUR = "h:mm:ss a"; /** * The default formatting pattern in 24-hour mode. This pattern is used * if {@link #setFormat24Hour(CharSequence)} is called with a null pattern * or if no pattern was specified when creating an instance of this class. * * This default pattern shows only the time, hours and minutes. * * @see #setFormat24Hour(CharSequence) * @see #getFormat24Hour() * * @deprecated Let the system use locale-appropriate defaults instead. */ public static final CharSequence DEFAULT_FORMAT_24_HOUR = "H:mm:ss"; private CharSequence mFormat12; private CharSequence mFormat24; @ExportedProperty private CharSequence mFormat; @ExportedProperty private boolean mHasSeconds; private boolean mAttached; private Calendar mTime; private String mTimeZone; private AlarmManager alarmManager; private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { chooseFormat(); onTimeChanged(); } @Override public void onChange(boolean selfChange, Uri uri) { chooseFormat(); onTimeChanged(); } }; private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) { final String timeZone = intent.getStringExtra("time-zone"); createTime(timeZone); } if (!ACTION_KEEP_WATCHFACE_AWAKE.equals(intent.getAction())) { onTimeChanged(); } } }; private final Runnable mTicker = new Runnable() { public void run() { onTimeChanged(); long now = SystemClock.uptimeMillis(); long next = now + (1000 - now % 1000); if (hasWatchface()) getWatchface().getHandler().postAtTime(mTicker, next); } }; private WeakReference<IWatchface> watchfaceRef; public Watch(IWatchface watchface) { if (watchface == null) { throw new AssertionError("Watchface can not be null"); } watchfaceRef = new WeakReference<IWatchface>(watchface); init(watchface); } private void init(@NonNull IWatchface watchface) { if (mFormat12 == null || mFormat24 == null) { Locale locale = watchface.getResources().getConfiguration().locale; if (mFormat12 == null) { mFormat12 = DEFAULT_FORMAT_12_HOUR; } if (mFormat24 == null) { mFormat24 = DEFAULT_FORMAT_24_HOUR; } } alarmManager = (AlarmManager) watchface.getContext().getSystemService(Context.ALARM_SERVICE); createTime(mTimeZone); // Wait until onAttachedToWindow() to handle the ticker chooseFormat(false); } private void createTime(String timeZone) { if (timeZone != null) { mTime = Calendar.getInstance(TimeZone.getTimeZone(timeZone)); } else { mTime = Calendar.getInstance(); } } /** * Returns the formatting pattern used to display the date and/or time * in 12-hour mode. The formatting pattern syntax is described in * {@link android.text.format.DateFormat}. * * @return A {@link CharSequence} or null. * * @see #setFormat12Hour(CharSequence) * @see #is24HourModeEnabled() */ @ExportedProperty public CharSequence getFormat12Hour() { return mFormat12; } /** * <p>Specifies the formatting pattern used to display the date and/or time * in 12-hour mode. The formatting pattern syntax is described in * {@link android.text.format.DateFormat}.</p> * * <p>If this pattern is set to null, {@link #getFormat24Hour()} will be used * even in 12-hour mode. If both 24-hour and 12-hour formatting patterns * are set to null, the default pattern for the current locale will be used * instead.</p> * * <p><strong>Note:</strong> if styling is not needed, it is highly recommended * you supply a format string generated by * {@link android.text.format.DateFormat#getBestDateTimePattern(java.util.Locale, String)}. This method * takes care of generating a format string adapted to the desired locale.</p> * * * @param format A date/time formatting pattern as described in {@link android.text.format.DateFormat} * * @see #getFormat12Hour() * @see #is24HourModeEnabled() * @see android.text.format.DateFormat#getBestDateTimePattern(java.util.Locale, String) * @see android.text.format.DateFormat * * @attr ref android.R.styleable#TextClock_format12Hour */ public void setFormat12Hour(CharSequence format) { mFormat12 = format; chooseFormat(); onTimeChanged(); } /** * Returns the formatting pattern used to display the date and/or time * in 24-hour mode. The formatting pattern syntax is described in * {@link android.text.format.DateFormat}. * * @return A {@link CharSequence} or null. * * @see #setFormat24Hour(CharSequence) * @see #is24HourModeEnabled() */ @ExportedProperty public CharSequence getFormat24Hour() { return mFormat24; } /** * <p>Specifies the formatting pattern used to display the date and/or time * in 24-hour mode. The formatting pattern syntax is described in * {@link android.text.format.DateFormat}.</p> * * <p>If this pattern is set to null, {@link #getFormat24Hour()} will be used * even in 12-hour mode. If both 24-hour and 12-hour formatting patterns * are set to null, the default pattern for the current locale will be used * instead.</p> * * <p><strong>Note:</strong> if styling is not needed, it is highly recommended * you supply a format string generated by * {@link android.text.format.DateFormat#getBestDateTimePattern(java.util.Locale, String)}. This method * takes care of generating a format string adapted to the desired locale.</p> * * @param format A date/time formatting pattern as described in {@link android.text.format.DateFormat} * * @see #getFormat24Hour() * @see #is24HourModeEnabled() * @see android.text.format.DateFormat#getBestDateTimePattern(java.util.Locale, String) * @see android.text.format.DateFormat * * @attr ref android.R.styleable#TextClock_format24Hour */ public void setFormat24Hour(CharSequence format) { mFormat24 = format; chooseFormat(); onTimeChanged(); } /** * Indicates whether the system is currently using the 24-hour mode. * * When the system is in 24-hour mode, this view will use the pattern * returned by {@link #getFormat24Hour()}. In 12-hour mode, the pattern * returned by {@link #getFormat12Hour()} is used instead. * * If either one of the formats is null, the other format is used. If * both formats are null, the default formats for the current locale are used. * * @return true if time should be displayed in 24-hour format, false if it * should be displayed in 12-hour format. * * @see #setFormat12Hour(CharSequence) * @see #getFormat12Hour() * @see #setFormat24Hour(CharSequence) * @see #getFormat24Hour() */ public boolean is24HourModeEnabled() { return hasWatchface() ? DateFormat.is24HourFormat(getWatchface().getContext()) : false; } public Calendar getTime() { return mTime; } /** * Indicates which time zone is currently used by this view. * * @return The ID of the current time zone or null if the default time zone, * as set by the user, must be used * * @see TimeZone * @see java.util.TimeZone#getAvailableIDs() * @see #setTimeZone(String) */ public String getTimeZone() { return mTimeZone; } /** * Sets the specified time zone to use in this clock. When the time zone * is set through this method, system time zone changes (when the user * sets the time zone in settings for instance) will be ignored. * * @param timeZone The desired time zone's ID as specified in {@link TimeZone} * or null to user the time zone specified by the user * (system time zone) * * @see #getTimeZone() * @see java.util.TimeZone#getAvailableIDs() * @see TimeZone#getTimeZone(String) * * @attr ref android.R.styleable#TextClock_timeZone */ public void setTimeZone(String timeZone) { mTimeZone = timeZone; createTime(timeZone); onTimeChanged(); } /** * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()} * depending on whether the user has selected 24-hour format. * * Calling this method does not schedule or unschedule the time ticker. */ private void chooseFormat() { chooseFormat(true); } /** * Returns the current format string. Always valid after constructor has * finished, and will never be {@code null}. * * @hide */ public CharSequence getFormat() { return mFormat; } /** * Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()} * depending on whether the user has selected 24-hour format. * * @param handleTicker true if calling this method should schedule/unschedule the * time ticker, false otherwise */ private void chooseFormat(boolean handleTicker) { final boolean format24Requested = is24HourModeEnabled(); //LocaleData ld = LocaleData.get(getContext().getResources().getConfiguration().locale); if (format24Requested) { mFormat = abc(mFormat24, mFormat12, DEFAULT_FORMAT_12_HOUR); } else { mFormat = abc(mFormat12, mFormat24, DEFAULT_FORMAT_24_HOUR); } boolean hadSeconds = mHasSeconds; mHasSeconds = !TextUtils.isEmpty(mFormat) ? mFormat.toString().contains(String.valueOf(DateFormat.SECONDS)) : false; if (hasWatchface()) { if (handleTicker && mAttached && hadSeconds != mHasSeconds) { if (hadSeconds) getWatchface().getHandler().removeCallbacks(mTicker); else mTicker.run(); } } } /** * Returns a if not null, else return b if not null, else return c. */ private static CharSequence abc(CharSequence a, CharSequence b, CharSequence c) { return a == null ? (b == null ? c : b) : a; } public void onAttachedToWindow() { if (!mAttached) { mAttached = true; registerReceiver(); registerObserver(); createTime(mTimeZone); if (hasWatchface() && getWatchface().handleSecondsInDimMode()) { alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000, 1000, getPendingIntent()); } if (mHasSeconds) { mTicker.run(); } else { onTimeChanged(); } } } public void onDetachedFromWindow() { if (mAttached) { unregisterReceiver(); unregisterObserver(); if (hasWatchface()) { getWatchface().getHandler().removeCallbacks(mTicker); if (getWatchface().handleSecondsInDimMode()) { alarmManager.cancel(getPendingIntent()); } } mAttached = false; } } private PendingIntent getPendingIntent() { return hasWatchface() ? PendingIntent.getBroadcast(getWatchface().getContext(), 0, new Intent(ACTION_KEEP_WATCHFACE_AWAKE), 0) : null; } private void registerReceiver() { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_TIME_TICK); filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); filter.addAction(ACTION_KEEP_WATCHFACE_AWAKE); if (hasWatchface()) getWatchface().getContext().registerReceiver(mIntentReceiver, filter, null, getWatchface().getHandler()); } private void registerObserver() { if (hasWatchface()) { final Context context = getWatchface().getContext(); final ContentResolver resolver = context.getContentResolver(); resolver.registerContentObserver(Settings.System.CONTENT_URI, true, mFormatChangeObserver); } } private void unregisterReceiver() { if (hasWatchface()) getWatchface().getContext().unregisterReceiver(mIntentReceiver); } private void unregisterObserver() { if (hasWatchface()) { final Context context = getWatchface().getContext(); final ContentResolver resolver = context.getContentResolver(); resolver.unregisterContentObserver(mFormatChangeObserver); } } private void onTimeChanged() { mTime.setTimeInMillis(System.currentTimeMillis()); if (hasWatchface()) { getWatchface().onTimeChanged(mTime); } } private boolean hasWatchface() { return watchfaceRef != null && watchfaceRef.get() != null; } private IWatchface getWatchface() { return hasWatchface() ? watchfaceRef.get() : null; } }